#include "TSintactico.h"

/** Comparador sin orden, para la lista de tokens */
int comparadorOrdenEntrada(void* elem1, void* elem2){
    return IGUAL;
}

int TSintactico_Crear(TSintactico* as){
    /* Setea la cantidad de tokens en cero */
    as->cantidadTokens = 0;
    /* Setea el codigo de ultimo error a OK sin error */
    as->codigoUltimoError = CODIGO_OK;
    /* Crea la lista de tokens */
    lso_Crear(&as->listaTokens, sizeof(Token), &comparadorOrdenEntrada);
    /* Inicializa el token como vacio */
    as->ultimoTokenProcesado.tipo=TOKEN_VACIO;
    /* Inicializo el indicador si se esta procesando un array */
    as->indicadorProcesandoArray=NO_PROCESANDO_ARRAY;
    as->tipoArray=TIPO_ARRAY_VACIO;
    /* Inicializa el nivel de anidacion en cero(0) */
    as->nivelAnidacion=0;
    /* Retorn codigo OK */
    return CODIGO_OK;
}


/********************** Llamadas a las funciones callback *********************/

int llamarFuncionCallback(TSintactico* analizadorSintactico, int codigoEvento, char* dato){
    return analizadorSintactico->registroFuncionCallback.funcionCallback(analizadorSintactico->registroFuncionCallback.contexto,
                                                                         codigoEvento,
                                                                         dato);
}

/********************** Analisis y Logica del PushToken ***********************/

/**
    TOKEN_STRING
    Posibles anteriores: TOKEN_OBJETO_EMPIEZA(nombre de un campo),
                         TOKEN_DOSPUNTOS(valor de un campo),
                         TOKEN_COMA(nombre de un campo, valor de un array de strings),
                         TOKEN_ARRAY_EMPIEZA(primer valor de un array de strings)

    TOKEN_NUMERO
    Posibles anteriores: TOKEN_DOSPUNTOS(el valor de un campo numerico),
                         TOKEN_COMA(un elemento de un array de numeros)
                         TOKEN_ARRAY_EMPIEZA(primer valor de un array de numeros)

    TOKEN_OBJETO_EMPIEZA
    Posibles anteriores: TOKEN_VACIO(en este caso es el primer objetoque se procesa)
                         TOKEN_DOSPUNTOS(en este caso es un objeto anidado)
                         TOKEN_OBJETO_TERMINA(arranca un nuevo tweet)

    TOKEN_OBJETO_TERMINA
    Posibles anteriores: TOKEN_STRING(valor de campo string, el ultimo del objeto)
                         TOKEN_NUMERO(valor de campo numerico, el ultimo del objeto)
                         TOKEN_ARRAY_TERMINA(cierre de array, el ultimo del objeto)
                         TOKEN_OBJETO_TERMINA(cierra de un objeto interno)

    TOKEN_ARRAY_EMPIEZA
    Posibles anteriores: TOKEN_DOSPUNTOS(empieza valor de un array)

    TOKEN_ARRAY_TERMINA
    Posibles anteriores: TOKEN_STRING(ultimo valor string, en caso de que el array sea de strings)
                         TOKEN_NUMERO(ultimo valor numerico, en caso de que el array sea de numeros)

    TOKEN_COMA
    Posibles anteriores: TOKEN_STRING(valor de un campo string, valor de una posicion de un array de strings)
                         TOKEN_NUMERO(valor de un campo numerico, valor de una posicion de un array de numeros)
                         TOKEN_ARRAY_FIN(fin de un valor de un campo de array)
                         TOKEN_TRUE(valor de un campo booleano, valor de una posicion de un array)
                         TOKEN_FALSE(valor de un campo booleano, valor de una posicion de un array)
                         TOKEN_NULL(valor de un campo, valor de una posicion de un array)

    TOKEN_DOSPUNTOS
    Posibles anteriores: TOKEN_STRING(nombre de un campo)

    TOKEN_TRUE
    Posibles anteriores: TOKEN_DOSPUNTOS(valor de un campo de tipo boolean)
                         TOKEN_ARRAY_EMPIEZA(primer valor booleano de un array de booleanos)
                         TOKEN_COMA(valor booleano de un array de booleanos)

    TOKEN_FALSE
    Posibles anteriores: TOKEN_DOSPUNTOS(valor de un campo de tipo boolean)
                         TOKEN_ARRAY_EMPIEZA(primer valor booleano de un array de booleanos)
                         TOKEN_COMA(valor booleano de un array de booleanos)

    TOKEN_NULL
    Posibles anteriores: TOKEN_DOSPUNTOS(valor de un campo)
                         TOKEN_ARRAY_EMPIEZA(primer valor de un array)
                         TOKEN_COMA(valor de un array)

**/

int determinarSiguienteCorrectoString(Token tokenAnterior, Token tokenActual){
    if (
          (tokenAnterior.tipo==TOKEN_OBJETO_EMPIEZA) ||
          (tokenAnterior.tipo==TOKEN_DOSPUNTOS)      ||
          (tokenAnterior.tipo==TOKEN_COMA)           ||
          (tokenAnterior.tipo==TOKEN_ARRAY_EMPIEZA)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoNumero(Token tokenAnterior, Token tokenActual){
    if (
          (tokenAnterior.tipo==TOKEN_DOSPUNTOS)      ||
          (tokenAnterior.tipo==TOKEN_COMA)           ||
          (tokenAnterior.tipo==TOKEN_ARRAY_EMPIEZA)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoObjetoEmpieza(Token tokenAnterior, Token tokenActual){
    if ( (tokenAnterior.tipo==TOKEN_VACIO)         ||
         (tokenAnterior.tipo==TOKEN_DOSPUNTOS)     ||
         (tokenAnterior.tipo==TOKEN_OBJETO_TERMINA)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoObjetoTermina(Token tokenAnterior, Token tokenActual){
    if (
          (tokenAnterior.tipo==TOKEN_STRING)         ||
          (tokenAnterior.tipo==TOKEN_NUMERO)         ||
          (tokenAnterior.tipo==TOKEN_ARRAY_TERMINA)  ||
          (tokenAnterior.tipo==TOKEN_OBJETO_TERMINA)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoArrayEmpieza(Token tokenAnterior, Token tokenActual, int indicadorProcesandoArray){
    if (
          (tokenAnterior.tipo==TOKEN_DOSPUNTOS)        &&
          (indicadorProcesandoArray!=PROCESANDO_ARRAY)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoArrayTermina(Token tokenAnterior, Token tokenActual, int indicadorProcesandoArray){
    if (
         (
          (tokenAnterior.tipo==TOKEN_STRING) ||
          (tokenAnterior.tipo==TOKEN_NUMERO)
         ) && (indicadorProcesandoArray==PROCESANDO_ARRAY)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoComa(Token tokenAnterior, Token tokenActual){
    if (
          (tokenAnterior.tipo==TOKEN_STRING)        ||
          (tokenAnterior.tipo==TOKEN_NUMERO)        ||
          (tokenAnterior.tipo==TOKEN_ARRAY_TERMINA) ||
          (tokenAnterior.tipo==TOKEN_TRUE) ||
          (tokenAnterior.tipo==TOKEN_FALSE) ||
          (tokenAnterior.tipo==TOKEN_OBJETO_TERMINA) ||
          (tokenAnterior.tipo==TOKEN_NULL)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoDosPuntos(Token tokenAnterior, Token tokenActual){
    if (
          (tokenAnterior.tipo==TOKEN_STRING)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoTrue(Token tokenAnterior, Token tokenActual){
    if (
         (tokenAnterior.tipo==TOKEN_DOSPUNTOS)     ||
         (tokenAnterior.tipo==TOKEN_ARRAY_EMPIEZA) ||
         (tokenAnterior.tipo==TOKEN_COMA)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoFalse(Token tokenAnterior, Token tokenActual){
    if (
         (tokenAnterior.tipo==TOKEN_DOSPUNTOS)     ||
         (tokenAnterior.tipo==TOKEN_ARRAY_EMPIEZA) ||
         (tokenAnterior.tipo==TOKEN_COMA)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrectoNull(Token tokenAnterior, Token tokenActual){
    if (
         (tokenAnterior.tipo==TOKEN_DOSPUNTOS)     ||
         (tokenAnterior.tipo==TOKEN_ARRAY_EMPIEZA) ||
         (tokenAnterior.tipo==TOKEN_COMA)
       ){
        return CODIGO_OK;
    }else{
        return CODIGO_ERROR_TOKEN_INVALIDO;
    }
}

int determinarSiguienteCorrecto(TSintactico* analizadorSintactico, Token tokenAnterior, Token tokenActual){
    /* Resultado de la operacion */
    int resultadoValidacion;
    /* Inicializa el resultado en OK, sin error */
    resultadoValidacion = CODIGO_OK;

    /* Depende el tipo de token actual, determina si es valido con respecto
       a el token anterior */
    if (tokenActual.tipo==TOKEN_STRING){
        /* Realiza la validacion del token */
        resultadoValidacion = determinarSiguienteCorrectoString(tokenAnterior, tokenActual);
        /* Si paso la validacion del token llama al callback correspondiente */
        if (resultadoValidacion==CODIGO_OK){
            /* Si el token es un String clave y no valor llama al callback clave */
            if (    (tokenAnterior.tipo==TOKEN_COMA && analizadorSintactico->indicadorProcesandoArray==NO_PROCESANDO_ARRAY)
                 || (tokenAnterior.tipo==TOKEN_OBJETO_EMPIEZA)
               ){
                /* Llama a la funcion callback de clave*/
                llamarFuncionCallback(analizadorSintactico, CB_CLAVE, tokenActual.dato);
            }
            /* Si no es un String clave es un String valor, llama al callback String */
            else{
                llamarFuncionCallback(analizadorSintactico, CB_STRING, tokenActual.dato);
            }
        }
    }
    else if (tokenActual.tipo==TOKEN_NUMERO){
        /* Realiza la validacion del token */
        resultadoValidacion = determinarSiguienteCorrectoNumero(tokenAnterior, tokenActual);
        /* Si paso la validacion llama al callback del numero */
        if (resultadoValidacion==CODIGO_OK){
            llamarFuncionCallback(analizadorSintactico, CB_NUMERO, tokenActual.dato);
        }
    }
    else if (tokenActual.tipo==TOKEN_OBJETO_EMPIEZA){
        /* Valida que sea valido sintacticamente el token */
        resultadoValidacion = determinarSiguienteCorrectoObjetoEmpieza(tokenAnterior, tokenActual);
        /* Si paso la validacion incremente el contador de nivel de anidamiento */
        if (resultadoValidacion==CODIGO_OK){
            analizadorSintactico->nivelAnidacion++;
        }
        /* Si es valido llama al callback asiciado al token */
        /* Ademas para llamar al callback el token anterior no tiene que
           ser DOS_PUNTOS, porque sino es un objeto anidado y en ese caso no hay que llamarla */
        /**if (resultadoValidacion==CODIGO_OK && tokenAnterior.tipo!=TOKEN_DOSPUNTOS){**/
            llamarFuncionCallback(analizadorSintactico, CB_COMIENZA_OBJETO, tokenActual.dato);
        /**}**/
    }
    else if (tokenActual.tipo==TOKEN_OBJETO_TERMINA){
        resultadoValidacion = determinarSiguienteCorrectoObjetoTermina(tokenAnterior, tokenActual);
        /* Si paso la validacion bien, decrementa la cantidad de objetos abiertos y
           llama a la funcion callback */
        if (resultadoValidacion==CODIGO_OK){
            analizadorSintactico->nivelAnidacion--;
            /* Cuando el nivel de anidacion llega a uno, termino el tweet
               llama a la callback terminar objeto y limpia el sintactico */
            llamarFuncionCallback(analizadorSintactico, CB_TERMINA_OBJETO, tokenActual.dato);
            if (analizadorSintactico->nivelAnidacion==0){
                TSintactico_terminarFlujo(analizadorSintactico);
            }
        }
    }
    else if (tokenActual.tipo==TOKEN_ARRAY_EMPIEZA){
        /* Valida el token array empieza */
        resultadoValidacion = determinarSiguienteCorrectoArrayEmpieza(tokenAnterior, tokenActual, analizadorSintactico->indicadorProcesandoArray);
        /* Si paso la validacion llama al callback array empieza */
        if (resultadoValidacion==CODIGO_OK){
            /* Setea el indicador en que se esta procesando un array */
            analizadorSintactico->indicadorProcesandoArray=PROCESANDO_ARRAY;
            /* Llama a la funcion callback de comienza array */
            llamarFuncionCallback(analizadorSintactico, CB_COMIENZA_ARRAY, tokenActual.dato);
        }
    }
    else if (tokenActual.tipo==TOKEN_ARRAY_TERMINA){
        /* Valida el token array termina */
        resultadoValidacion = determinarSiguienteCorrectoArrayTermina(tokenAnterior, tokenActual, analizadorSintactico->indicadorProcesandoArray);
        /* Si paso la validacion llama a callback de array termina */
        if (resultadoValidacion==CODIGO_OK){
            /* Setea indicador en que NO se esta procesando un array */
            analizadorSintactico->indicadorProcesandoArray=NO_PROCESANDO_ARRAY;
            /* Llama a callback de array termina */
            llamarFuncionCallback(analizadorSintactico, CB_TERMINA_ARRAY, tokenActual.dato);
        }
    }
    else if (tokenActual.tipo==TOKEN_COMA){
        resultadoValidacion = determinarSiguienteCorrectoComa(tokenAnterior, tokenActual);
    }
    else if (tokenActual.tipo==TOKEN_DOSPUNTOS){
        /* Valida que el token sea valido sintacticamente */
        resultadoValidacion = determinarSiguienteCorrectoDosPuntos(tokenAnterior, tokenActual);
    }
    else if (tokenActual.tipo==TOKEN_TRUE){
        /* Realiza la validacion del token true actual */
        resultadoValidacion = determinarSiguienteCorrectoTrue(tokenAnterior, tokenActual);
        /* Si pasa la validacion llama al callback */
        if (resultadoValidacion==CODIGO_OK){
            llamarFuncionCallback(analizadorSintactico, CB_TRUE, tokenActual.dato);
        }
    }
    else if (tokenActual.tipo==TOKEN_FALSE){
        /* Realiza la validacion del token */
        resultadoValidacion = determinarSiguienteCorrectoFalse(tokenAnterior, tokenActual);
        /* Si pasa la validacion llama al callback del false */
        if (resultadoValidacion==CODIGO_OK){
            llamarFuncionCallback(analizadorSintactico, CB_FALSE, tokenActual.dato);
        }
    }
    else if (tokenActual.tipo==TOKEN_NULL){
        /* Realiza la validacion del token */
        resultadoValidacion = determinarSiguienteCorrectoNull(tokenAnterior, tokenActual);
        /* Si pasa la validacion llama al callback del token null */
        if (resultadoValidacion==CODIGO_OK){
            llamarFuncionCallback(analizadorSintactico, CB_NULL, tokenActual.dato);
        }
    }
    /* Devuelve el resultado de la validacion */
    return resultadoValidacion;
}

Token obtenerTokenAnterior(TSintactico* as){
    return as->ultimoTokenProcesado;
}

int procesarToken(TSintactico* as, Token* token){
    /* Pone el token en la lista de tokens */
    lso_Insertar(&as->listaTokens, token);
    /* Setea el token procesado como token anterior */
    as->ultimoTokenProcesado=*token;
    /* Incrementa la cantidad de tokens */
    as->cantidadTokens++;
    /* TODO BORRAR, Muestra el contenido del token procesado */
    /* printf("Tipo del token:  %d\n", token->tipo); */
    /* printf("Valor del token: %s\n\n", token->dato); */
    /* Retorna codigo de OK */
    return CODIGO_OK;
}

int TSintactico_PushToken(TSintactico* as, Token* token){
    /* Resultado de la insercion del token */
    int resultado;
    /* Token anterior */
    Token tokenAnterior;

    /* Inicializa el resultado en OK, sin error */
    resultado = CODIGO_OK;

    /* Obtiene el token anterior */
    tokenAnterior = obtenerTokenAnterior(as);

    /* Evalua si el token es valido con respecto a los anteriores */
    resultado = determinarSiguienteCorrecto(as, tokenAnterior, *token);
    /* Si el token es valido lo inserta en la lista */
    if ( resultado==CODIGO_OK ){
        procesarToken(as, token);
    }
    /* Retorna el resultado de la operacion */
    return resultado;
}

int TSintactico_terminarFlujo(TSintactico* as){
    /* Limpia la lista de tokens */
    lso_Vaciar(&as->listaTokens);
    /* Para resetear los valores a los iniciales
       llama al Crear */
    TSintactico_Crear(as);
    /* Retorna codigo de OK, sin error */
    return CODIGO_OK;
}

int TSintactico_getUltimoError(TSintactico* as, int *codigo, char* mensaje){
    /* Setea en el codigo parametro el ultimo codigo de error */
    *codigo = as->codigoUltimoError;
    /* Copia el mensaje de error al parametro mensaje */
    strcpy(mensaje, recuperarMensajeError(as->codigoUltimoError));
    /* Retorna codigo de OK, sin errores */
    return CODIGO_OK;
}

int TSintactico_setCallback(TSintactico* ts, int evento, void* tda_contexto, int(*callback)(const void*, int, const void*) ){
    /* Setea los datos al registro de funcion callback */
    ts->registroFuncionCallback.codigoEvento = evento;
    ts->registroFuncionCallback.contexto = tda_contexto;
    ts->registroFuncionCallback.funcionCallback = callback;
    /* Retorna codigo OK */
    return CODIGO_OK;
}
